package org.infodavid.common.messaging;

import java.io.Serializable;
import java.lang.reflect.Constructor;

import org.infodavid.common.messaging.exceptions.MessagingException;
import org.infodavid.common.messaging.impl.MessagingServiceImpl;

/**
 * The Class MessagingService.
 */
public abstract class MessagingService {

	/**
	 * The Class MessagingOptions.
	 */
	public static class MessagingOptions {

		/** Default priority. */
		public static final byte DEFAULT_PRIORITY = 4;

		/** Default time to live in ms. */
		public static final int DEFAULT_TIME_TOLIVE = 0;

		/** auto acknowledge mode. */
		private boolean autoAcknowledge = true;

		/** persistent delivery mode. */
		private boolean persistentDelivery = true;

		/** time to live in ms. */
		private long timeToLiveInMillis = DEFAULT_TIME_TOLIVE;

		/** priority. */
		private int priority = DEFAULT_PRIORITY;

		/**
		 * Default constructor.
		 */
		public MessagingOptions() {
			super();
		}

		/**
		 * Default constructor.
		 * @param model the model
		 */
		public MessagingOptions(final MessagingOptions model) {
			super();

			setAutoAcknowledge(model.isAutoAcknowledge());
			setPersistentDelivery(model.isPersistentDelivery());
			setPriority(model.getPriority());
			setTimeToLiveInMillis(model.getTimeToLiveInMillis());
		}

		/*
		 * (non-Javadoc)
		 * @see java.lang.Object#clone()
		 */
		@Override
		public MessagingOptions clone() throws CloneNotSupportedException {
			return (MessagingOptions)super.clone();
		}

		/**
		 * Gets the default length of time in milliseconds from its dispatch time that a produced
		 * message should be retained by the message system.
		 * @return the timeToLiveInMillis
		 */
		public long getTimeToLiveInMillis() {
			return timeToLiveInMillis;
		}

		/**
		 * Returns the auto-acknowledge mode.
		 * @return the true if acknowledge mode is auto
		 */
		public boolean isAutoAcknowledge() {
			return autoAcknowledge;
		}

		/**
		 * Fets if message is persisted.
		 * @return the persistentDelivery
		 */
		public boolean isPersistentDelivery() {
			return persistentDelivery;
		}

		/**
		 * Sets the auto-acknowledge mode.
		 * @param flag true to set the acknowledge mode to auto
		 * @return the messaging options
		 */
		public MessagingOptions setAutoAcknowledge(final boolean flag) {
			autoAcknowledge = flag;

			return this;
		}

		/**
		 * Sets if message is persisted.
		 * @param value the persistent delivery to set
		 * @return the messaging options
		 */
		public MessagingOptions setPersistentDelivery(final boolean value) {
			persistentDelivery = value;

			return this;
		}

		/**
		 * Sets the default length of time in milliseconds from its dispatch time that a produced
		 * message should be retained by the message system.
		 * @param value the time to live in ms to set
		 * @return the messaging options
		 */
		public MessagingOptions setTimeToLiveInMillis(final long value) {
			if (value < 0) {
				timeToLiveInMillis = 0;
			}
			else {
				timeToLiveInMillis = value;
			}

			return this;
		}

		/**
		 * Gets the priority.
		 * @return the priority
		 */
		public int getPriority() {
			return priority;
		}

		/**
		 * Sets the priority.
		 * @param value the priority: 0 the lowest to 9 the highest.Clients should consider priorities
		 *          0-4 as gradations of normal priority and priorities 5-9 as gradations of expedited
		 *          priority. Priority is set to 4 by default.
		 * @return the messaging options
		 */
		public MessagingOptions setPriority(final int value) {
			if (value < 0 || value > 9) {
				throw new IllegalArgumentException(
				    "the priority is not valid: 0 the lowest to 9 the highest");
			}

			priority = value;

			return this;
		}

		/**
		 * Returns the string representation of the state of the object.
		 * @return the string representation of the state of the object
		 */
		@Override
		public String toString() {
			final StringBuffer buffer = new StringBuffer();

			buffer.append(super.toString());
			buffer.append('\n');

			buffer.append('[');
			buffer.append("autoAcknowledge = ").append(autoAcknowledge);
			buffer.append(" persistentDelivery = ").append(persistentDelivery);
			buffer.append(" timeToLiveInMillis = ").append(timeToLiveInMillis);
			buffer.append(" priority = ").append(priority);
			buffer.append(']');

			return buffer.toString();
		}
	}

	/**
	 * The Interface IListener.
	 */
	public interface IListener {

		/**
		 * Handle textual messages.
		 * @param text the text
		 * @param timestamp the timestamp
		 * @param messageId the message id
		 * @return true to send acknowledge.
		 * @throws Exception the exception
		 */
		boolean onTextMessage(String text, long timestamp, String messageId) throws Exception;

		/**
		 * Handle object messages.
		 * @param object the object
		 * @param timestamp the timestamp
		 * @param messageId the message id
		 * @return true to send acknowledge.
		 * @throws Exception the exception
		 */
		boolean onObjectMessage(Object object, long timestamp, String messageId) throws Exception;
	}

	/**
	 * The Interface IConnection.
	 */
	public interface IConnection {

		/**
		 * Connect.
		 * @throws Exception the exception
		 */
		void connect() throws Exception;

		/**
		 * Disconnect.
		 * @throws Exception the exception
		 */
		void disconnect() throws Exception;
	}

	/** INSTANCE. */
	private static final MessagingService SINGLETON;
	static {
		try {
			final Constructor<MessagingServiceImpl> cons =
			    MessagingServiceImpl.class.getDeclaredConstructor();
			cons.setAccessible(true);

			SINGLETON = cons.newInstance();
		}
		catch (final Exception e) {
			throw new Error("Unable to build a MessagingService singleton", e);
		}
	}

	/**
	 * Returns the instance of the singleton.
	 * @return The instance.
	 */
	public static MessagingService getInstance() {
		return SINGLETON;
	}

	/** options. */
	private final MessagingOptions options = new MessagingOptions();

	/**
	 * Send a textual message to the specified queue.
	 * @param queueName the queue name
	 * @param texts the texts
	 */
	public abstract void sendTextMessages(String queueName, String... texts)
	    throws MessagingException;

	/**
	 * Send a message to the specified queue to a given address.
	 * @param address the address described by: hostname:port
	 * @param queueName the queue name
	 * @param objects the objects
	 */
	public abstract void sendObjectMessages(String address, String queueName, Serializable... objects)
	    throws MessagingException;

	/**
	 * Close connections.
	 */
	public abstract void closeConnections();

	/**
	 * Returns the messaging options.
	 * @return the options
	 */
	public MessagingOptions getOptions() {
		return options;
	}

	/**
	 * Send a message to the specified queue.
	 * @param queueName the queue name
	 * @param objects the objects
	 */
	public abstract void sendObjectMessages(String queueName, Serializable... objects)
	    throws MessagingException;

	/**
	 * Send a textual message to the specified queue to a given address.
	 * @param address the address described by: hostname:port
	 * @param queueName the queue name
	 * @param texts the texts
	 */
	public abstract void sendTextMessages(String address, String queueName, String... texts)
	    throws MessagingException;

	/**
	 * Listen to messages and notify listener.
	 * @param address the address
	 * @param queueName the queue name
	 * @param listener the listener
	 * @return the i connection
	 * @throws MessagingException The exception
	 */
	public abstract IConnection listen(final String address, final String queueName,
	    final IListener listener) throws MessagingException;

	/**
	 * Send a message to the specified queue to a given address.
	 * @param address the address described by: hostname:port
	 * @param queueName the queue name
	 * @param pOptions the options
	 * @param objects the objects
	 */
	public abstract void sendObjectMessages(String address, String queueName,
	    MessagingOptions pOptions, Serializable... objects) throws MessagingException;

	/**
	 * Send a message to the specified queue to a given address.
	 * @param queueName the queue name
	 * @param pOptions the options
	 * @param objects the objects
	 */
	public abstract void sendObjectMessages(String queueName, MessagingOptions pOptions,
	    Serializable... objects) throws MessagingException;

	/**
	 * Send a textual message to the specified queue to a given address.
	 * @param address the address described by: hostname:port
	 * @param queueName the queue name
	 * @param pOptions the options
	 * @param texts the texts
	 */
	public abstract void sendTextMessages(String address, String queueName,
	    MessagingOptions pOptions, String... texts) throws MessagingException;

	/**
	 * Send a textual message to the specified queue to a given address.
	 * @param queueName the queue name
	 * @param pOptions the options
	 * @param texts the texts
	 */
	public abstract void sendTextMessages(String queueName, MessagingOptions pOptions,
	    String... texts) throws MessagingException;
}
